import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.LinkedList;


public class CorrectorFilter extends InstrumentationFilterFramework{
	/*************************************************************
	*	This filter corrects "wild" values in the data 
	*	Data that is need for the checking process is passed on immediately
	*************************************************************/

	//output files
	private static File wildFile;

	//pressure constants
	private static double MAX_PRESSURE;
	private static double MIN_PRESSURE;

	//lists to hold data before written to file
	final private LinkedList<Long> times = new LinkedList<Long>();
	final private LinkedList<Double> psi = new LinkedList<Double>();

	//Formatting section
	//CHANGE: Originally, the date format was "yyyy MM dd::hh:mm:ss:SSS"
	final private Calendar TimeStamp = Calendar.getInstance();
	final private SimpleDateFormat TimeStampFormat = new SimpleDateFormat("yyyy:DD:HH:mm:ss");
	final private DecimalFormat psiFormat = new DecimalFormat("00.00000");
	//end of formatting

	CorrectorFilter(String fileName, double min, double max){
		wildFile = new File(fileName);
		MAX_PRESSURE = max;
		MIN_PRESSURE = min;
	}
	
	public void run()
	{
		boolean firstValidPsi=false;
		boolean newTimeOrPSI = false;
		Double lastValidPsi = new Double(-1.0);
		int smallestIndex = 0;						//used to keep track of the psi and time 
													//indices in case the data from the stream arrives
													//out of sync.

		int MeasurementLength = 8;		// This is the length of all measurements (including time) in bytes
		int IdLength = 4;				// This is the length of IDs in the byte stream

		byte databyte = 0;				// This is the data byte read from the stream
		int bytesread = 0;				// This is the number of bytes read from the stream

		long measurement;				// This is the word used to store all measurements - conversions are illustrated.
		int id;							// This is the measurement id
		int i;							// This is a loop counter

		/*************************************************************
		 *	First we announce to the world that we are alive...
		 **************************************************************/

		System.out.print( "\n" + this.getName() + "::Corrector Reading ");


		try{
			wildFile.delete();
			wildFile.createNewFile();
			FileWriter wildWriter = new FileWriter(wildFile);
			wildWriter.write("Time: \t\tPressure (PSI):\n");

			while (true)
			{
				try
				{
					/*************************************************************
					 *	Read id
					 **************************************************************/

					id = 0;

					for (i=0; i<IdLength; i++ )
					{
						databyte = ReadFilterInputPort();	// This is where we read the byte from the stream...

						id = id | (databyte & 0xFF);		// We append the byte on to ID...

						if (i != IdLength-1)				// If this is not the last byte, then slide the
						{									// previously appended byte to the left by one byte
							id = id << 8;					// to make room for the next byte we append to the ID

						} // if

						bytesread++;						// Increment the byte count

					} // for

					/*************************************************************
					 *	Read measurements
					 **************************************************************/

					measurement = 0;

					for (i=0; i<MeasurementLength; i++ )
					{
						databyte = ReadFilterInputPort();
						measurement = measurement | (databyte & 0xFF);	// We append the byte on to measurement...

						if (i != MeasurementLength-1)					// If this is not the last byte, then slide the
						{												// previously appended byte to the left by one byte
							measurement = measurement << 8;				// to make room for the next byte we append to the
							// measurement
						} // if

						bytesread++;									// Increment the byte count

					} // if

					/*************************************************************
					 *	Classify the measurement
					 **************************************************************/

					if ( id == Measurement.TIME.getID() )
					{
						//adds the time measurement
						times.add(new Long(measurement));

						//sends the time measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.TIME, measurement);

						newTimeOrPSI=true;
					} // if
					
					else if ( id == Measurement.VELOCITY.getID())
					{	
						//convert to double
						double vel = Double.longBitsToDouble(measurement);

						//sends the altitude measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.VELOCITY, new Double(vel));

					} // if

					else if ( id == Measurement.ALTITUDE.getID())
					{	
						//convert to double
						double altMeters = Double.longBitsToDouble(measurement);

						//sends the altitude measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.ALTITUDE, new Double(altMeters));

					} // if

					else if ( id == Measurement.PRESSURE.getID() )
					{	
						//convert to double
						double pressure = Double.longBitsToDouble(measurement);

						//adds the pressure measurement
						psi.add(new Double(pressure));

						newTimeOrPSI = true;
					} // if

					else if ( id == Measurement.TEMPERATURE.getID() )
					{
						//convert to double
						double celsius = (Double.longBitsToDouble(measurement));

						//sends the temperature measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.TEMPERATURE, new Double(celsius));

					} // if

					else if ( id == Measurement.PITCH.getID() )
					{
						//convert to double
						double pit = (Double.longBitsToDouble(measurement));

						//sends the temperature measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.PITCH, new Double(pit));

					} // if


					/*************************************************************
					 *	Sets index to prevent time and pressure information from being out-of-sync
					 **************************************************************/
					if (times.size()>psi.size()){
						smallestIndex = psi.size()-1;
					}
					else {
						smallestIndex = times.size()-1;
					}
					
					/*************************************************************
					 *	Should the time and psi information be checked?
					 **************************************************************/
					if(		newTimeOrPSI &&
							smallestIndex > -1
					){

						Double examinedPsi = psi.get(smallestIndex); //the psi being examined

						/*************************************************************
						 *	Check for new wild points
						 **************************************************************/
						if (!isValidPsi(examinedPsi)){

							/*************************************************************
							 *	Handling wild point
							 **************************************************************/
							writeWildPoint(wildWriter, times.getLast(), examinedPsi);

							//the wild point will remain in the list
						}
						
						/*************************************************************
						 *	Handling a valid pressure
						 **************************************************************/
						else{
							//this checks for the first valid pressure case 
							if (firstValidPsi == false){
								firstValidPsi = true;
								
								//removes the aligned time and psi information and sends the psi measurement
								for (i = 0; i<smallestIndex;i++){
									removeAndSendPressure("Sent First Copied Valid Pressure(s): ", examinedPsi);
								}
								
								removeAndSendPressure("Sent First Valid Pressure: ", examinedPsi);
								
								//records the value to be used for computing later
								lastValidPsi = examinedPsi;
								
							}
							
							else{
								/*************************************************************
								 *	handles the case of several wild points still in the list
								 **************************************************************/
								
								if (
										times.size()>1 &&
										psi.size()>1
								){
									
									//compute the corrected value using the lastValidPsi, 
									//record it into the lastValidPsi
									//remove the aligned time and psi information,  
									for(i=0; i<smallestIndex; i++){
										System.out.println("Computing pressure: ");
										System.out.println("Last valid pressure: "+lastValidPsi);
										System.out.println("New valid pressure: "+examinedPsi + "\n");
										
										lastValidPsi=(lastValidPsi+examinedPsi)/2.0;
										
										removeAndSendPressure("Sent Computed Pressure: ", lastValidPsi);
									}
									
									//send the valid pressure value
									
									removeAndSendPressure("Sent Valid Pressure After Wild Points: ", examinedPsi);
									
									//records the value to be potentially used for averaging later
									lastValidPsi = examinedPsi;
									
								}
								/*************************************************************
								 *	no wild points in the list
								 **************************************************************/
								else{
									
									removeAndSendPressure("Sent Valid Pressure: ", examinedPsi);
									lastValidPsi = examinedPsi;
								}
							}
						}
					}

				} // try

				catch (EndOfStreamException e)
				{
					/*************************************************************
					 *	Handling no valid pressure values at the end of the stream and no valid pressure values cases 
					 **************************************************************/

					while (psi.size()>0){
						removeAndSendPressure("Sent Last Valid Pressure(s): ", lastValidPsi);
					}
					ClosePorts();
					System.out.print( "\n" + this.getName() + "::Corrector Exiting; bytes read: " + bytesread +"\n");
					break;

				} // catch
				newTimeOrPSI = false;
			} // while

		}catch (IOException ioE){
			ioE.printStackTrace();
		}

	} // run

	private boolean isValidPsi(Double psi){
		//if the value of the pressure is greater than 80 or less than 50
		if(
				psi.doubleValue() <= MAX_PRESSURE &&
				psi.doubleValue() >= MIN_PRESSURE ){
			System.out.println(psi + " is valid\n");
			return true;
		}
		else{
			System.out.println(psi + " is a wild point!\n");
			return false;
		}
	}
	private void writeWildPoint(FileWriter wildWriter, Long time, Double pressure) throws IOException{
		//writes the wild pressure point to file
		//this abstracts away the formatting of the writing, while keeping it consistent, in case the spacing needs to be changed
		TimeStamp.setTimeInMillis(time);
		wildWriter.write("\n"+TimeStampFormat.format(TimeStamp.getTime())+"\t"+ psiFormat.format(pressure.doubleValue()));
		wildWriter.flush();
	}

	private void removeAndSendPressure(String message, Double psiToSend){
		System.out.println("Time: "+times.remove()+" removed");
		System.out.println("Pressure: "+psi.remove()+" removed");
		System.out.println(
				message +
				writeMeasurementToFilterOutputPort(Measurement.PRESSURE, new Double(psiToSend)) +
				"\n"
		);
	}
	/*
	private void printContents(){
		System.out.println("Times:");
		for (int i =0; i<times.size();i++){
			System.out.println(times.get(i));
		}
		System.out.println("Pressures:");
		for (int i =0; i<psi.size();i++){
			System.out.println(psi.get(i));
		}
	}
*/
}

